//
// triangIntersection class - Intersect triangles
// Author: Antonio Miranda - Fev 2010
//

// brief description:
// - First, the mesh are inserted internally by insert method. Here,
//   the bounding box of triangles are inserted into a R-Tree (m_globalTree),
//   checking intersection of boxes. All possibles intersected elements are
//   store into dataIntersect.elems variable. This intersection is just a
//   first aproximation. The bounding box of element stores the information
//   about its parent mesh (reference.Mesh), id at mesh and if the element
//   id was added list of possible intersected elements (dataIntersect.elems).
// - The current 

#include <iostream>

#include "triangIntersection.h"

#include "rtree/src/amr3bind.h"
#include "ctopology/topology.hpp"
#include "geom/box3d.h"                // geomlib
#include "fastTriangInterTest.h"



using namespace std;

// local structure

class attrElem;

// class dataIntersect - This class store a mesh of triangles and a list
// of triangles (elements) that was detected any intersection of elements
class dataIntersect
{
public:
  cTopology *topMesh;  // current mesh
  int             id;  // id mesh
  vector <int> elems;  // list of elements that was detected intersection  
                       // with other elements in current topMesh
  attrElem *elemAttr;  // vector (size of n. elements) that store attribute of triangle  
};

// attribute (reference) for an element   
class attrElem
{
public:
  int  id;              // id of element
  dataIntersect *Mesh;  // mesh that elment belongs
  int  add;             // this flag means that element was added to list of
                        // intersected elements

  vector <attrElem *>  interElm;  // elements that intersect this element
};


// triangIntersection
/////////////////////////////////////////////////////////////////////
triangIntersection::triangIntersection( void )
{
  m_globalTree = RtreeCreate( );
  m_data.clear();
}

// ~triangIntersection
/////////////////////////////////////////////////////////////////////
triangIntersection::~triangIntersection(void)
{

}

// ~insert
/////////////////////////////////////////////////////////////////////
void triangIntersection::insert( void *_mesh)
{
  dataIntersect *mesh = new dataIntersect;

  mesh->topMesh = (cTopology *) _mesh;

  // insert mesh to a vector
  m_data.push_back(mesh);
  mesh->id = (int) m_data.size();

  // insert elements of mesh to tree if this mesh is the first inserted
  if (m_data.size() == 1)
  {
    insertMeshIntoTree (mesh);
    return;
  }

  // insert elements of mesh to tree and check intersection
  insertMeshIntoTreeAndCheck (mesh);
}

/////////////////////////////////////////////////////////////////////
void triangIntersection::report()
{

  for (int i = 0; i < (int) m_data.size(); ++i)
  {
    cTopology *topMesh  = m_data[i]->topMesh;
    attrElem  *elemAttr = m_data[i]->elemAttr;
    int        nelem   = topMesh->NumElems();

    std::cout << endl << endl <<"Mesh " << i << endl;

    for (int j = 0; j < nelem; ++j)
    {

      if (elemAttr[j].add) // triangle was added as intersected
      {
        std::cout << "Element " << j << " = ";

        // for all intersected triangles
        int nelem = (int) elemAttr[j].interElm.size(); 
        for (int k = 0; k < nelem; ++k)
        {
          attrElem *currAdjInter = elemAttr[j].interElm[k];
          std::cout << "(" << currAdjInter->Mesh->id << ", "<< currAdjInter->id << ") ";
        }
        
        std::cout << endl;
      }

    }
  }
}


void triangIntersection::newreport() // Ricardo Marques 08/03/2010
{
  for (int i = 0; i < (int) m_data.size(); ++i)
  {
    cTopology *topMesh  = m_data[i]->topMesh;
    attrElem  *elemAttr = m_data[i]->elemAttr;
    int        nelem   = topMesh->NumElems();

    for (int j = 0; j < nelem; ++j)
    {

      if (elemAttr[j].add) // triangle was added as intersected
      {
        // for all intersected triangles
        int nelem = (int) elemAttr[j].interElm.size(); 
        for (int k = 0; k < nelem; ++k)
        {
          attrElem *currAdjInter = elemAttr[j].interElm[k];
          printf(  "Elem %d of Mesh %d INTERSECTED Elem %d of Mesh %d\n",
              j, i, currAdjInter->id, currAdjInter->Mesh->id - 1  );
        }
      }
    }
    printf("\n");
  }

}
int
triangIntersection::GetCandidatesNumber() // RicardoMarques 09/03/2010
{
  int IntTriNum = 0;
  for (int i = 0; i < (int) m_data.size(); ++i)
  {
    cTopology *topMesh  = m_data[i]->topMesh;
    attrElem  *elemAttr = m_data[i]->elemAttr;
    int        nelem   = topMesh->NumElems();

    for (int j = 0; j < nelem; ++j)
    {
      if (elemAttr[j].add) // triangle was added as intersected
      {
        IntTriNum += (int) elemAttr[j].interElm.size(); 
      }
    }
  }

  return IntTriNum;
}
void
triangIntersection::GetCandidates(int* CandidatesArray)
{
  int ArrayIndex=0;

  for (int i = 0; i < (int) m_data.size(); ++i)
  {
    cTopology *topMesh  = m_data[i]->topMesh;
    attrElem  *elemAttr = m_data[i]->elemAttr;
    int        nelem   = topMesh->NumElems();

    for (int j = 0; j < nelem; ++j)
    {

      if (elemAttr[j].add) // triangle was added as intersected
      {
        // for all intersected triangles
        int nelem = (int) elemAttr[j].interElm.size(); 
        for (int k = 0; k < nelem; ++k)
        {
          attrElem *currAdjInter = elemAttr[j].interElm[k];
          CandidatesArray[ArrayIndex++]=j;
          CandidatesArray[ArrayIndex++]=i;
          CandidatesArray[ArrayIndex++]=currAdjInter->id;
          CandidatesArray[ArrayIndex++]=(currAdjInter->Mesh)->id - 1;
        }
      }
    }
  }
}

/////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////
//  private methods
/////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////


// insertMeshIntoTree
// given a mesh, this method insert triangles to rtree.
/////////////////////////////////////////////////////////////////////
void triangIntersection::insertMeshIntoTree( dataIntersect *_mesh )
{
  Box3D box;
  double coord[3];
  double xmin, xmax, ymin, ymax, zmin, zmax;

  cTopology *topMesh = _mesh->topMesh;
  if (topMesh == NULL)
    return;

  // number of elements
  int n_elem = topMesh->NumElems();
  if (n_elem == 0)
    return;

  // open reference to elements
  _mesh->elemAttr = new attrElem[n_elem];
  attrElem *refElem = _mesh->elemAttr;

  // for all elements
  for (int i = 0; i < n_elem; ++i)
  {
    box.clear();
    int nnodes = topMesh->GetElemNNodes (i);
    int *conn  = topMesh->GetElemConn(i);

    // set element intersection as zero
    topMesh->SetElemInfo (i, 0);

    // get bound box of element
    for (int h = 0; h < nnodes; ++h)
    {
      topMesh->GetCoordNode (conn[h], coord);
      box.update(coord[0], coord[1], coord[2]);
    }
    box.expande(2.0); // expande in 2%

    // insert box to range tree
    refElem[i].Mesh = _mesh;
    refElem[i].id   = i;
    refElem[i].add  = 0;
    box.get(xmin, ymin, zmin, xmax, ymax, zmax);
    RtreeInsert (m_globalTree, (void*) &refElem[i], xmin, xmax, ymin, ymax, zmin, zmax);
  }

}

// insertMeshIntoTreeAndCheck
// given a mesh, this method first check if there is any intersection and
// insert triangles to Rtree. In the same time, the attribute of element,
// reference to Rtree, is update to inform id of triangle, each mesh belong,
// and that the triangle was added to list of intersection.
/////////////////////////////////////////////////////////////////////
void triangIntersection::insertMeshIntoTreeAndCheck( dataIntersect *_mesh )
{
  Box3D box;
  double coord[3];
  double xmin, xmax, ymin, ymax, zmin, zmax;
  double xmn, xmx, ymn, ymx, zmn, zmx;

  cTopology *topMesh = _mesh->topMesh;
  if (topMesh == NULL)
    return;

  // number of elements
  int n_elem = topMesh->NumElems();
  if (n_elem == 0)
    return;

  // open reference to elements
  _mesh->elemAttr = new attrElem[n_elem];
  attrElem *refElem = _mesh->elemAttr;

  // for all elements
  for (int i = 0; i < n_elem; ++i)
  {
    box.clear();
    int nnodes = topMesh->GetElemNNodes (i);
    int *conn  = topMesh->GetElemConn(i);

    // set element intersection as zero
    topMesh->SetElemInfo (i, 0);

    // get bound box of element
    for (int h = 0; h < nnodes; ++h)
    {
      topMesh->GetCoordNode (conn[h], coord);
      box.update(coord[0], coord[1], coord[2]);
    }
    box.expande(2.0); // expande in 2%

    // insert box to range tree
    refElem[i].Mesh = _mesh;
    refElem[i].id   = i;
    refElem[i].add  = 0;
    box.get(xmin, ymin, zmin, xmax, ymax, zmax);

    // try to find intersect boxes
    RtreeInitSearchBox (m_globalTree, xmin, xmax, ymin, ymax, zmin, zmax);
    void *info;
    int find = 0;
    while ((info = RtreeSearchBox (m_globalTree, &xmn, &xmx, &ymn, &ymx, &zmn, &zmx)) != NULL)
    {
      attrElem *elem = (attrElem *) info;

      // check for different mesh
      if (elem->Mesh != _mesh)
      {
        // check for real intersection
        if (checkElemIntersection (elem->Mesh, elem->id, _mesh, i))
        {
          elem->Mesh->topMesh->SetElemInfo (elem->id, 1);
          find = 1;

          if (!elem->add) // it not added to structure
          {
            elem->Mesh->elems.push_back(elem->id);
            elem->add = 1;
          }

          addTriangleIntesect(elem, &refElem[i]);
        }

      }
    }
    
    // insert current element into rtree
    RtreeInsert (m_globalTree, (void*) &refElem[i], xmin, xmax, ymin, ymax, zmin, zmax);
 
    // set current element flag
    if (find)
    {
      topMesh->SetElemInfo (i, 1);
      _mesh->elems.push_back(i);
      refElem[i].add = 1;
    }
  }
}


/////////////////////////////////////////////////////////////////////
bool 
triangIntersection::checkElemIntersection
( 
  dataIntersect *_mesh0, 
  int _elem0, 
  dataIntersect *_mesh1, 
  int _elem1 
)
{
  double V0[3], V1[3], V2[3];
  double U0[3], U1[3], U2[3];
  int    *conn;

  // checks - consider only triangular elements
  if (_mesh0->topMesh->GetElemNNodes(_elem0) != 3)
    return false;
  if (_mesh1->topMesh->GetElemNNodes(_elem1) != 3)
    return false;

  // get vertex of element 0
  conn = _mesh0->topMesh->GetElemConn(_elem0);
  _mesh0->topMesh->GetCoordNode(conn[0], V0);
  _mesh0->topMesh->GetCoordNode(conn[1], V1);
  _mesh0->topMesh->GetCoordNode(conn[2], V2);

  // get vertex of element 1
  conn = _mesh1->topMesh->GetElemConn(_elem1);
  _mesh1->topMesh->GetCoordNode(conn[0], U0);
  _mesh1->topMesh->GetCoordNode(conn[1], U1);
  _mesh1->topMesh->GetCoordNode(conn[2], U2);

  if (fastTriangInterTest::intersect (V0, V1, V2, U0, U1, U2))
    return true;

  return false;
}

/////////////////////////////////////////////////////////////////////
void triangIntersection::addTriangleIntesect( attrElem *tri01, attrElem *tri02 )
{
  tri01->interElm.push_back(tri02);
  tri02->interElm.push_back(tri01);
}